-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/#13 retrofit base api #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @rhkrwngud445, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
이 PR은 OkHttp와 Retrofit을 사용하여 앱의 기본 API 통신 인프라를 구축하고, 임시 토큰 주입 기능을 추가하며, 홈 화면에 예시 API 연동을 구현합니다. 이를 통해 향후 API 기반 기능 개발을 위한 기반을 마련합니다.
Highlights
- 네트워크 스택 설정: OkHttp와 Retrofit 라이브러리를 추가하고, Kotlin Serialization을 위한 컨버터를 설정했습니다.
- API 환경 설정: build.gradle.kts에 개발 및 프로덕션 환경에 따른 API 기본 URL과 디버그 빌드용 임시 토큰을 BuildConfig 필드로 추가했습니다.
- 인터넷 권한 추가: AndroidManifest.xml에 인터넷 사용 권한을 선언했습니다.
- Hilt 의존성 주입: 네트워크 모듈(NetworkModule, ServiceModule)과 리포지토리 모듈(RepositoryModule)을 Hilt를 사용하여 구성하여 OkHttp 클라이언트, Retrofit 인스턴스, API 서비스, 그리고 친구 데이터 리포지토리(FriendRepository)를 주입할 수 있도록 했습니다.
- 토큰 인터셉터 구현: OkHttp 인터셉터를 통해 모든 API 요청에 임시 인증 토큰을 자동으로 추가하도록 구현했습니다.
- 친구 데이터 모델 및 서비스 추가: 친구 목록을 가져오는 API 서비스(FriendService), 네트워크 응답을 위한 FriendEntity, 그리고 앱 내에서 사용할 Friend 모델 및 매퍼를 정의했습니다.
- 홈 화면 API 연동: 홈 화면 ViewModel을 FriendRepository를 사용하도록 업데이트하여 친구 목록 데이터를 가져오고 UI에 표시할 수 있도록 했습니다.
- 초기 시작 화면 변경: 앱 실행 시 시작 화면을 친구 프로필 편집기에서 홈 화면으로 변경했습니다.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
이 PR은 Retrofit과 OkHttp를 사용하여 기본 API 통신 계층을 설정하는 중요한 작업을 수행합니다. Hilt를 사용한 의존성 주입, Kotlinx Serialization을 사용한 데이터 직렬화, 임시 토큰을 사용한 인증 등 좋은 관행들이 많이 적용되었습니다. 몇 가지 개선 사항을 제안합니다. 주요 내용은 보안(토큰 저장), 에러 처리, 코드 안정성 및 유지보수성 향상에 중점을 둡니다. 전반적으로 프로젝트의 네트워킹 기반을 잘 다지는 훌륭한 시작입니다.
| fun provideLoggingInterceptor(): HttpLoggingInterceptor = | ||
| HttpLoggingInterceptor().let { | ||
| if (BuildConfig.DEBUG) { | ||
| it.setLevel(HttpLoggingInterceptor.Level.BODY) | ||
| } else { | ||
| it.setLevel(HttpLoggingInterceptor.Level.NONE) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 provideLoggingInterceptor 함수는 HttpLoggingInterceptor를 반환해야 하지만, let 블록의 마지막 구문인 it.setLevel(...)이 Unit을 반환하므로 실제로는 Unit이 반환되어 컴파일 오류가 발생합니다. 객체 생성 후 속성을 설정하는 경우에는 apply 스코프 함수를 사용하는 것이 더 관용적이고 안전합니다.
| fun provideLoggingInterceptor(): HttpLoggingInterceptor = | |
| HttpLoggingInterceptor().let { | |
| if (BuildConfig.DEBUG) { | |
| it.setLevel(HttpLoggingInterceptor.Level.BODY) | |
| } else { | |
| it.setLevel(HttpLoggingInterceptor.Level.NONE) | |
| } | |
| } | |
| fun provideLoggingInterceptor(): HttpLoggingInterceptor = | |
| HttpLoggingInterceptor().apply { | |
| level = if (BuildConfig.DEBUG) { | |
| HttpLoggingInterceptor.Level.BODY | |
| } else { | |
| HttpLoggingInterceptor.Level.NONE | |
| } | |
| } |
| } | ||
| debug { | ||
| buildConfigField("String", "NEAR_URL", getProperty("NEAR_DEV_URL")) | ||
| buildConfigField("String", "TEMP_TOKEN", getProperty("TEMP_TOKEN")) // TODO 추후 삭제 필요 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| override fun fetchFriends(): Flow<List<Friend>> = | ||
| flow { | ||
| emit( | ||
| friendService.fetchFriends().map { | ||
| it.toModel() | ||
| }, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 DefaultFriendRepository의 fetchFriends 함수는 API 호출 시 발생할 수 있는 예외(네트워크 오류 등)를 처리하지 않습니다. 예외가 발생하면 flow가 비정상적으로 종료되고, 이를 수집하는 ViewModel에서 처리하지 않으면 앱이 강제 종료될 수 있습니다. try-catch 블록을 사용하여 예외를 처리하고, 도메인 특화 예외를 던지거나 Result와 같은 래퍼 클래스를 사용하여 결과를 반환하는 것이 좋습니다.
override fun fetchFriends(): Flow<List<Friend>> =
flow {
try {
emit(
friendService.fetchFriends().map {
it.toModel()
},
)
} catch (e: Exception) {
// TODO: 도메인에 맞는 에러로 변환하여 처리하거나 로깅 필요
throw e
}
}| val friendsFlow: StateFlow<List<Friend>> = | ||
| friendRepository | ||
| .fetchFriends() | ||
| .stateIn( | ||
| scope = viewModelScope, | ||
| started = SharingStarted.WhileSubscribed(5_000), | ||
| initialValue = HomeUiState.Loading, | ||
| initialValue = emptyList(), | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
friendRepository.fetchFriends()에서 발생하는 예외를 처리하지 않고 stateIn으로 StateFlow를 만들고 있습니다. 만약 네트워크 요청이 실패하여 예외가 발생하면, 이 Flow를 구독하는 UI 레이어에서 앱이 비정상 종료될 수 있습니다. catch 연산자를 사용하여 예외를 처리하고, 사용자에게 오류 상태를 알리거나 안정적인 상태(예: 빈 목록)를 반환하도록 처리해야 합니다.
| val friendsFlow: StateFlow<List<Friend>> = | |
| friendRepository | |
| .fetchFriends() | |
| .stateIn( | |
| scope = viewModelScope, | |
| started = SharingStarted.WhileSubscribed(5_000), | |
| initialValue = HomeUiState.Loading, | |
| initialValue = emptyList(), | |
| ) | |
| val friendsFlow: StateFlow<List<Friend>> = | |
| friendRepository | |
| .fetchFriends() | |
| .catch { exception -> | |
| // TODO: 에러 로깅 및 사용자에게 보여줄 에러 상태 처리 | |
| emit(emptyList()) // 임시로 빈 리스트를 반환하여 크래시 방지 | |
| } | |
| .stateIn( | |
| scope = viewModelScope, | |
| started = SharingStarted.WhileSubscribed(5_000), | |
| initialValue = emptyList(), | |
| ) |
| implementation(libs.kotlin.serialization.json) | ||
| } | ||
|
|
||
| fun getProperty(propertyKey: String): String = gradleLocalProperties(rootDir, providers).getProperty(propertyKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
com.android.build.gradle.internal 패키지에 속한 API는 Gradle의 내부 구현에 해당하므로 직접 사용하는 것은 위험합니다. Gradle 버전이 업데이트될 때 예고 없이 변경되거나 제거될 수 있어, 향후 빌드 실패의 원인이 될 수 있습니다. java.util.Properties를 사용하여 local.properties 파일을 직접 읽는 것이 더 안정적이고 권장되는 방법입니다.
fun getProperty(propertyKey: String): String {
val properties = java.util.Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.inputStream().use { input ->
properties.load(input)
}
}
return properties.getProperty(propertyKey) ?: error("'$propertyKey' not found in local.properties")
}| interface FriendService { | ||
| @GET("/friend/list") | ||
| suspend fun fetchFriends(): List<FriendEntity> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엔드포인트 경로 /friend/list가 문자열로 하드코딩되어 있습니다. 이렇게 '매직 스트링'을 사용하면 오타가 발생하기 쉽고, 나중에 경로를 변경할 때 여러 곳을 수정해야 하는 번거로움이 있습니다. companion object 내에 상수로 정의하여 재사용성과 유지보수성을 높이는 것이 좋습니다.
| interface FriendService { | |
| @GET("/friend/list") | |
| suspend fun fetchFriends(): List<FriendEntity> | |
| interface FriendService { | |
| @GET(FRIEND_LIST) | |
| suspend fun fetchFriends(): List<FriendEntity> | |
| companion object { | |
| private const val FRIEND_LIST = "/friend/list" | |
| } | |
| } |
작업 내용
확인 방법
홈 화면 진입, 로그 확인
참고 사항
세부 에러 핸들링 시, 에러 로직 구성 필요
관련 이슈